/**
 * Copyright (c) 2006-2015 Hzins Ltd. All Rights Reserved.
 * <p>
 * This code is the confidential and proprietary information of
 * Hzins. You shall not disclose such Confidential Information
 * and shall use it only in accordance with the terms of the agreements
 * you entered into with Hzins,http://www.hzins.com.
 */
package com.huize.ladon.apm.common.util;


import com.huize.ladon.apm.common.domain.TraceIdWrapper;
import com.huize.ladon.apm.common.exception.ApmRuntimeException;
import org.apache.commons.lang3.StringUtils;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 调用链ID工具类
 *
 * @author hz15051315
 * @date 2019年3月20日 上午10:39:24
 */
public final class TraceIdUtils {

    /**
     * sequence序列号最小值
     */
    public static final int MIN_SEQUENCE = 1000;
    /**
     * sequence序列号最大值
     */
    public static final int MAX_SEQUENCE = 9999;

    /**
     * 总长度29 = ip(8) + timestamp(13) + pid(4) + sequence(4)
     */
    public static final int TRACE_ID_LENGTH = 29;

    /**
     * 最大进程号
     * <p>
     * （1）linux系统，PID范围是1--32767(也就是0x7fff)
     * （2）Windows系统,PID值可以达到20W以上，例如234236，因此校验PID值没啥意义
     * </p>
     */
    private static final int MAX_PID = 32767; // 也就是0x7fff

    /**
     * 最小进程号
     */
    private static final int MIN_PID = 1;

    /**
     * Sequence计器数
     */
    private static AtomicInteger count = new AtomicInteger(1000);

    /**
     * 获取TraceId末尾4位自增数字,从1000-9999循环生成
     *
     * @return
     */
    private static int getNextSequence() {
        for (; ; ) {
            int current = count.get();
            //FIXME 数字超过9999后，此时需要大于等于条件，否则next值等于10000，将会导致traceId长度异常 add by tianyuliang 2019-10-10
            int next = current >= MAX_SEQUENCE ? MIN_SEQUENCE : current + 1;
            if (count.compareAndSet(current, next)) {
                return next;
            }
        }
    }


    /**
     * 构建traceId（总长度为29）格式： ip(8) + timestamp(13) + pid(4) + sequence(4)
     *
     * @param ip  IPV4地址(e.g. 192.168.145.76)
     * @param pid 进程PID
     * @return
     * @author hz15051315
     * @date 2018年1月11日 上午11:35:50
     * @version
     */
    public static String createTraceId(String ip, int pid) {
        return createTraceId(ip, System.currentTimeMillis(), pid, getNextSequence());
    }

    /**
     * 构建traceId（总长度为29）格式： ip(8) + timestamp(13) + pid(4) + sequence(4)
     *
     * @param ip        IPV4地址(e.g. 192.168.145.76)
     * @param timestamp 时间戳，毫秒
     * @param pid       进程PID
     * @param sequence  自增序号，1000-9999之间的数字
     * @return
     */
    public static String createTraceId(String ip, Long timestamp, Integer pid, int sequence) {
        if (MixAllUtils.isEmpty(ip)) {
            throw new ApmRuntimeException(String.format("create traceId failed, ip = %s is empty.", ip));
        }
        if (DateTimeUtils.parseMilliTime(timestamp) == null) {
            throw new ApmRuntimeException(String.format("create traceId failed, timestamp = %d is invalid.", timestamp));
        }
        if (pid == null || pid < 0) {
            pid = 0;
            // pid取值范围在Linux与Windows系统差异巨大，校验它的取值范围没啥意义,故取消校验
            // throw new ApmRuntimeException(String.format("create traceId failed, pid = %d is invalid.", pid));
        }

        if (sequence < MIN_SEQUENCE || sequence > MAX_SEQUENCE) {
            throw new ApmRuntimeException(String.format("create traceId failed, sequence = %d is not in [1000, 9999]", sequence));
        }

        String hexIp = encodeIp(ip);
        String hexPid = encodePid(pid);

        StringBuffer sb = new StringBuffer(40);
        sb.append(hexIp).append(timestamp).append(hexPid).append(sequence);
        return sb.toString();
    }

    /**
     * 解析traceId
     *
     * @param traceId 链路ID，格式： ip(8) + timestamp(13) + pid(4) + sequence(4)
     * @return
     */
    public static TraceIdWrapper resolveTraceId(String traceId) {
        if (StringUtils.isBlank(traceId) || traceId.length() != TRACE_ID_LENGTH) {
            throw new ApmRuntimeException("traceId为空 或 traceId长度无效!");
        }

        String hexIp = traceId.substring(0, 8);
        String timestamp = traceId.substring(8, 21);
        String hexPid = traceId.substring(21, 25);
        String sequence = traceId.substring(25, 29);

        TraceIdWrapper bean = new TraceIdWrapper();
        bean.setIp(decodeIp(hexIp));
        bean.setTimestamp(MixAllUtils.parseLong(timestamp, 0L));
        bean.setPid(decodePid(hexPid));
        bean.setSequence(MixAllUtils.parseInteger(sequence, 0));
        return bean;
    }

    /**
     * 10进制PID进程号转化为二进制
     *
     * @param pid
     * @return
     */
    public static String encodePid(int pid) {
        byte[] buf = ByteUtils.intToBytes(pid, 2);
        String hexPid = ByteUtils.binary2Hex(buf);        // linux max pid(Short.MAX) 正整数时，导致精度丢失
        return hexPid;
    }

    /**
     * 二进制PID进程号转化为10进制
     *
     * @param hexPid
     * @return
     */
    public static int decodePid(String hexPid) {
        byte[] bytes = ByteUtils.hex2Binary(hexPid);
        int pid = ByteUtils.bytesToInt(bytes);
        return pid;
    }

    /**
     * IPV4地址进行16进制转化
     *
     * @param ip
     * @return
     * @author hz15051315
     * @date 2018年1月11日 上午11:36:13
     * @version
     */
    public static String encodeIp(String ip) {
        int ip_long = ip2long(ip);                                  // ip2long
        byte[] bytes = ByteUtils.intToBytes(ip_long, 4);       // long2bytes
        String hex = ByteUtils.binary2Hex(bytes);                   // bytes2hex
        return hex;
    }

    /**
     * 16进制IP地址转化为字符串IPV4
     *
     * @param hexIp 16进制IP地址
     * @return
     * @author hz15051315
     * @date 2018年1月11日 上午11:35:50
     * @version
     */
    public static String decodeIp(String hexIp) {
        String str_ip = fillHexIp(hexIp);                     // 补足长度
        byte[] buf = ByteUtils.hex2Binary(str_ip);            // hex2bytes
        long ip_long = ByteUtils.bytesToLong(buf);            // bytes2long
        return long2ip(ip_long);
    }

    /**
     * 补足十六进制IP长度
     *
     * @param hexIp
     * @return
     */
    private static String fillHexIp(String hexIp) {
        int length = 16;
        if (hexIp.length() >= length) {
            return hexIp;
        }

        for (int i = hexIp.length(); i < length; i++) {
            hexIp = "0" + hexIp;
        }
        return hexIp;
    }

    /**
     * 把字符串IP转化为长整型数字
     *
     * @param strIp
     * @return
     */
    private static int ip2long(String strIp) {
        int[] ip = new int[4];
        // 先找到IP地址字符串中.的位置
        int position1 = strIp.indexOf(".");
        int position2 = strIp.indexOf(".", position1 + 1);
        int position3 = strIp.indexOf(".", position2 + 1);
        // 将每个.之间的字符串转换成整型
        ip[0] = Integer.parseInt(strIp.substring(0, position1));
        ip[1] = Integer.parseInt(strIp.substring(position1 + 1, position2));
        ip[2] = Integer.parseInt(strIp.substring(position2 + 1, position3));
        ip[3] = Integer.parseInt(strIp.substring(position3 + 1));
        return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3];
    }

    /**
     * 把长整型数字转化为字符串IP
     *
     * @param longIp
     * @return
     */
    private static String long2ip(long longIp) {
        StringBuffer sb = new StringBuffer(30);
        // 直接右移24位
        sb.append(String.valueOf((longIp >>> 24)));
        sb.append(".");
        // 将高8位置0，然后右移16位
        sb.append(String.valueOf((longIp & 0x00FFFFFF) >>> 16));
        sb.append(".");
        // 将高16位置0，然后右移8位
        sb.append(String.valueOf((longIp & 0x0000FFFF) >>> 8));
        sb.append(".");
        // 将高24位置0
        sb.append(String.valueOf((longIp & 0x000000FF)));
        return sb.toString();
    }

    public static void main(String[] args) {
        String ip = "10.168.13.224";
        int pid = 8941;
        Long timestamp = 1554085943497L;
        int sequence = 2391;
        String traceId = createTraceId(ip, timestamp, pid, sequence);
        System.out.println(traceId);

        String binaryPid = ByteUtils.binary2Hex(ByteUtils.intToBytes(pid, 2));
        System.out.println("pid=" + pid + ", binaryPid=" + binaryPid);

        String hexIp = encodeIp(ip);
        System.out.println("ip=" + decodeIp(hexIp) + ", hexIp=" + hexIp);

        String testTraceId = "0aa80de0155408594349722ed2391";
        TraceIdWrapper bean = resolveTraceId(testTraceId);
        System.out.println("traceId=" + testTraceId + ", " + bean.toString());

        // createTraceId(null, timestamp, pid, sequence);
        // createTraceId(ip, 0L, pid, sequence);
        // createTraceId(ip, timestamp, pid, 19);
    }


}
